#include "types.h"
#include "loader.h"
#include "os_io.h"
#include "process.h"
#include "scheduler.h"
#include "ksem.h"
#include "string.h"

extern uint32_t __usr__stack__base__;
extern uint32_t __usr__stack__len__;
extern void _start_process(void);

#define USER_STACK_MAX_LEN ((uint32_t)&__usr__stack__len__)


static uint8_t *g_user_stack = NULL;
static uint16_t running_counter = 0;

//static pcb_t g_os_pcb[MAX_PROCESSES] __attribute__((section(".os_mem")));
static pcb_t g_os_pcb[MAX_PROCESSES]; 
static uint8_t *g_pcb_freelist = (uint8_t *)g_os_pcb;

extern pcb_t *g_running_process;
extern pcb_t *os_process;

static pcb_t *g_process_list[MAX_PROCESSES] = {NULL};

void dump_processes(void)
{
    uint16_t i = 0;
    pcb_t *pcb = NULL;

    for (i = 0; i < MAX_PROCESSES; i++) {
        if (g_process_list[i]) {
	    pcb = g_process_list[i];
	    printf("%s 0x%x\n", pcb->name, pcb->process_state);
	}
    }

    return;
}

pcb_t *get_current_process(void)
{
    return g_running_process;
}

uint32_t get_current_pid(void)
{
    return GET_PID_FROM_PCB(g_running_process);
}

pcb_t *get_process(uint32_t pid)
{
    uint16_t pid_index = GET_PID_INDEX_FROM_PID(pid);
    if (pid_index < MAX_PROCESSES) { 
	return g_process_list[pid_index];
    } else {
	return NULL;
    }
}

bool is_valid_pid(uint32_t pid)
{
    uint16_t pid_index = 0;
    uint16_t run_count = 0;
    pcb_t *pcb_ptr = NULL;

    pid_index = GET_PID_INDEX_FROM_PID(pid);
    if (pid_index > MAX_PROCESSES) {
        return FALSE;
    } else {
	run_count = GET_RUNNING_COUNT_FROM_PID(pid);
	pcb_ptr = g_process_list[pid_index];
	if (pcb_ptr->running_counter != run_count) {
	    return FALSE;
	} else {
	    return TRUE;
	}
    }
}



        

void loader_init(void)
{
    uint32_t region_len = (uint32_t)&__usr__stack__len__;
    // The stack range for user plane stacks is divided into regions of size DEFAULT_USER_STACK_SIZE
    g_user_stack = (uint8_t *)&__usr__stack__base__;
    mem_region_init(g_user_stack, region_len, sizeof(stack_info_t));

    mem_blocks_init(g_pcb_freelist, sizeof(pcb_t), MAX_PROCESSES);

    return;
}

bool load_program(void (*addr)(void), const uint8_t *name, uint32_t stack_size, uint8_t process_priority)
{
    stack_info_t *ptr;
    uint32_t pid_index;

    if (process_priority > PROCESS_LOWEST_PRIORITY)
    {
	printf("Invalid process priority: 0x%x\n", process_priority);
	return FALSE;
    }


    // Stack size should be a multiple of 4
    if (stack_size & 0x3) {
	stack_size = (stack_size & 0xFFFFFFFC) + 4;
    }

    if (!alloc_mem_buffer(g_user_stack, stack_size, (mem_buffer_t **)&ptr))
    {
	printf ("Stack allocation fail. Aborting\n");
	return FALSE;
    }

    pcb_t *pcb_ptr = (pcb_t *)alloc_mem_block(&g_pcb_freelist);
    if (!pcb_ptr) 
    {
	printf("MAX PROCESSES(0x%x) exceeded\n", MAX_PROCESSES);
	free_mem_buffer((mem_buffer_t *)ptr);
	return FALSE;
    }
    printf("Process %s, pid:0x%x launched at address: 0x%x\n", name, (uint32_t)GET_PID_FROM_PCB(pcb_ptr), (uint32_t)pcb_ptr);

    pcb_ptr->stack_ptr = ptr;
    pcb_ptr->process_state = process_ready;
    pcb_ptr->process_priority = process_priority;
    pcb_ptr->running_counter = running_counter++;
    strncpy(pcb_ptr->name, name, sizeof(pcb_ptr->name));

    pid_index = GET_PID_INDEX_FROM_PCB(pcb_ptr);
    g_process_list[pid_index] = pcb_ptr;

    // Initialize device list
    pcb_ptr->device_list.next = NULL;
    pcb_ptr->device_list.prev = &pcb_ptr->device_list;

    // Initialize queues information
    pcb_ptr->owned_queues = NULL;

    uint32_t *stack_ptr = (uint32_t *)((uint32_t)ptr + sizeof(stack_info_t) + ptr->buffer_len - 4);

    //*stack_ptr = (uint32_t)addr;	// Actual address to jump on is stored on the allocated stack

    pcb_ptr->regs[REGSET_SP_INDEX] = (uint32_t)stack_ptr;
    pcb_ptr->regs[REGSET_PC_INDEX] = (uint32_t)&_start_process + 4;  /* we jump at pc -4 */
    pcb_ptr->regs[REGSET_PSR_INDEX] = 0x1F;	// user mode, ARM mode, interrupts enabled and all condition flags 0
    pcb_ptr->regs[REGSET_R0_INDEX] = (uint32_t)addr;

    // move to end of ready queue
    add_process_to_ready_queue(pcb_ptr);

    //printf("kernel: Created and added program at address 0x%x in ready queue\n", (uint32_t)addr);
    return TRUE;
}


void exec_process(uint32_t process_info)
{
    process_info_t *p_info = (process_info_t *)process_info;
    //printf("Going to load process with priority: 0x%x\n", p_info->process_priority);

    if (p_info->stack_size == 0)
    {
	// provide default stack size
	p_info->stack_size = DEFAULT_USER_STACK_SIZE;		
    }

    load_program(p_info->addr, p_info->name, p_info->stack_size, p_info->process_priority);

}

void free_process_memory(pcb_t *pcb)
{
    uint32_t pid_index = 0;
    dev_entry_t *dev_entry = NULL;
    dev_entry_t *prev_dev_entry = NULL;

    // Cleanup devices opened by the process
    dev_entry = pcb->device_list.next;
    while (dev_entry) {
        cleanup_device(&dev_entry->access_info);
	prev_dev_entry = dev_entry;
	dev_entry = dev_entry->next;
	free(prev_dev_entry);
    }
    pcb->device_list.next = NULL;
    pcb->device_list.prev = &pcb->device_list;

    // Cleanup queues maintained by the process
    printf("Freeing process memory for %s\n", pcb->name);
    while (pcb->owned_queues) {
        _delete_queue(pcb->owned_queues->queue_id);
    }

    pid_index = GET_PID_INDEX_FROM_PCB(pcb);
    g_process_list[pid_index] = NULL;

    free_mem_buffer(pcb->stack_ptr);
    free_mem_block(&g_pcb_freelist, (mem_block_t *)g_running_process);

    return;
}

void exit_process(uint32_t exit_code)
{
    g_running_process->process_state = process_dead;

    if (g_running_process->blocked_res) {
	// User case for a blocked process exiting ??
	ksem_remove_process_from_queue(g_running_process);
    }

    //printf("kernel: Marked current process for termination\n");

    return;
}


uint32_t *create_os_process(void)
{
    uint32_t stack_size = DEFAULT_USER_STACK_SIZE;
    stack_info_t *st_info = NULL;
    pcb_t *pcb_ptr = NULL;
    uint16_t pid_index = 0;

    if (!alloc_mem_buffer(g_user_stack, stack_size, (mem_buffer_t **)&st_info))
    {
	printf("Stack allocation fail for OS process\n");
	return NULL;
    }

    pcb_ptr = (pcb_t *)alloc_mem_block(&g_pcb_freelist);
    if (!pcb_ptr)
    {
	printf("MAX PROCESSES(0x%x) exceeded\n", MAX_PROCESSES);
	free_mem_buffer((mem_buffer_t *)st_info);
	return NULL;
    }
    printf("Created OS process at location: 0x%x\n", (uint32_t)pcb_ptr);

    pcb_ptr->stack_ptr = st_info;
    pcb_ptr->process_state = process_running;
    pcb_ptr->process_priority = PROCESS_LOWEST_PRIORITY;
    pcb_ptr->running_counter = running_counter++;
    strncpy(pcb_ptr->name, (const uint8_t *)"os_process", sizeof(pcb_ptr->name));
    
    pid_index = GET_PID_INDEX_FROM_PCB(pcb_ptr);
    g_process_list[pid_index] = pcb_ptr;

    os_process = pcb_ptr;
    g_running_process = pcb_ptr;

    uint32_t *stack_ptr = (uint32_t *)((uint8_t *)st_info + sizeof(mem_buffer_t) + st_info->buffer_len - 4);

    printf("Allocated stack for OS process at: 0x%x, stack_info:0x%x, stack_size:0x%x\n", \
	    (uint32_t)stack_ptr, (uint32_t)st_info, st_info->buffer_len);

    return stack_ptr;
}

